/*
 *
 *
 */

#include "user_common.h"
#include "user_stash.h"

user_stash_s g_user_stash = {
	.c_flash_info = NULL,
	.c_partition_header = NULL,
};

/************************************************************
 * |------------------|
 * | partition header |
 * |------------------|
 * |       info       |
 * |------------------|
 * |       data       |
 * |------------------|
 ************************************************************/

int user_stash_save(uint32_t type, 
		uint32_t offset, uint32_t size, void *data)
{
	user_stash_partition_header_s partition_header;
	int retval, try_times;

	gkt_info("user_stash_save: type(%u) offset(0x%x) size(0x%x)\n", 
		type, offset, size);

	if (g_user_stash.c_flash_info) {
		if ((USER_STASH_PARTITION_HEADER_SIZE + size) > g_user_stash.c_flash_info->size) {
			gkt_error("user_stash_save: overflow!\n");
			return GKT_EOVERFLOW;
		}

		memset(&partition_header, 0xff, USER_STASH_PARTITION_HEADER_SIZE);
		partition_header.common.type = type;
		partition_header.common.magic = USER_MAGIC;
		partition_header.info.offset = offset;
		partition_header.info.size = size;
	
		try_times = USER_FLASH_TRYTIMES;
		do {
			retval = gkt_flash_partition_erase_all(g_user_stash.c_flash_info->id);
			if (retval != GKT_SUCCESS) {
				gkt_error("user_stash_save: erase failed %d\n", retval);
				continue;
			}

			retval = gkt_flash_partition_write(g_user_stash.c_flash_info->id,
							USER_STASH_PARTITION_HEADER_SIZE, size, data, 0);
			if (retval != GKT_SUCCESS) {
				gkt_error("user_stash_save: write partition data failed %d\n", retval);
				continue;
			}
			user_test_pd_on_flash_write("user_stash_save - data");

			partition_header.common.state = USER_PARTITION_ST_SAVING;
			retval = gkt_flash_partition_write(g_user_stash.c_flash_info->id,
							0, USER_STASH_PARTITION_HEADER_SIZE, 
							&partition_header, 0);
			if (retval != GKT_SUCCESS) {
				gkt_error("user_stash_save: write partition header failed %d\n", retval);
				continue;
			}
			user_test_pd_on_flash_write("user_stash_save - header");

			partition_header.common.state = USER_PARTITION_ST_NORMAL;
			retval = gkt_flash_partition_write(g_user_stash.c_flash_info->id,
							__MEMBER_OFFSET(user_stash_partition_header_s, common.state), 
							sizeof(partition_header.common.state), 
							&partition_header.common.state, 0);
			if (retval != GKT_SUCCESS) {
				gkt_error("user_stash_save: update state failed %d\n", retval);
				continue;
			}
			user_test_pd_on_flash_write("user_stash_save - state");

			break;
		} while (--try_times > 0);
	}
	else
		retval = GKT_EPERMIT;

	return retval;
}

int user_stash_apply(const gkt_flash_partition_info_s *c_flash_info, 
			void *buffer)
{
	user_partition_header_s *v_partition_header;
	uint32_t alignmask, offset, size;
	int try_times, retval;

	alignmask = c_flash_info->sector_size - 1;
	offset = g_user_stash.c_partition_header->info.offset;
	size = g_user_stash.c_partition_header->info.size;
	gkt_info("user_stash_apply: offset(0x%x) size(0x%x)\n", offset, size);

	if ((offset & alignmask) || (size > c_flash_info->sector_size)) {
		gkt_error("user_stash_apply: offset or size invalid.\n");
		return GKT_EUNMATCHED;
	}

	retval = gkt_flash_partition_read(g_user_stash.c_flash_info->id,
					USER_STASH_PARTITION_HEADER_SIZE, size, buffer);
	if (retval != GKT_SUCCESS) {
		gkt_error("user_stash_apply: read failed %d\n", retval);
		return retval;
	}

	try_times = 0;
	do {
		retval = gkt_flash_partition_erase(c_flash_info->id,
						offset, c_flash_info->sector_size);
		if (retval != GKT_SUCCESS) {
			gkt_error("user_stash_apply: erase failed %d\n", retval);
			continue;
		}

		if (!offset) {
			v_partition_header = (user_partition_header_s *)buffer;
			v_partition_header->state = USER_PARTITION_ST_SAVING;
		}

		retval = gkt_flash_partition_write(c_flash_info->id,
						offset, size, buffer, 0);
		if (retval != GKT_SUCCESS) {
			gkt_error("user_stash_apply: write failed %d\n", retval);
			continue;
		}

		if (!offset) {
			v_partition_header->state = USER_PARTITION_ST_RECYCLE_STARTED;
			retval = gkt_flash_partition_write(c_flash_info->id,
						__MEMBER_OFFSET(user_partition_header_s, state),
						sizeof(v_partition_header->state), &v_partition_header->state, 0);
			if (retval != GKT_SUCCESS) {
				gkt_error("user_stash_apply: update state failed %d\n", retval);
				continue;
			}
		}

		break;
	} while (++try_times < USER_FLASH_TRYTIMES);

	return retval;
}

void user_stash_reset(void)
{
	int retval;

	gkt_info("user_stash_reset: ...\n");

	if (g_user_stash.c_flash_info) {
		if (!g_user_stash.c_flash_info->read_map_addr
			&& g_user_stash.v_partition_header) 
		{
			memset(g_user_stash.v_partition_header, 0xff, 
						USER_STASH_PARTITION_HEADER_SIZE);
		}

		retval = gkt_flash_partition_erase_all(g_user_stash.c_flash_info->id);
		if (retval != GKT_SUCCESS)
			gkt_error("user_stash_reset: reset partition failed %d\n", retval);
	}
}

static user_stash_partition_header_s s_user_stash_partition_header;
void user_stash_init(void)
{
	int retval;

	g_user_stash.c_flash_info = gkt_flash_partition_get_info("UserStash");
	if (g_user_stash.c_flash_info) {
		gkt_trace("user_stash_init: id(%d), size(0x%x)\n",
				g_user_stash.c_flash_info->id,
				g_user_stash.c_flash_info->size);

		if (g_user_stash.c_flash_info->read_map_addr) {
			g_user_stash.c_partition_header = (const user_stash_partition_header_s *)
					g_user_stash.c_flash_info->read_map_addr;
		}
		else {
			g_user_stash.v_partition_header = &s_user_stash_partition_header;
			retval = gkt_flash_partition_read(g_user_stash.c_flash_info->id,
						0, USER_STASH_PARTITION_HEADER_SIZE, 
						g_user_stash.v_partition_header);
			if (retval != GKT_SUCCESS) {
				gkt_error("user_stash_init: read failed %d\n", retval);
				memset(g_user_stash.v_partition_header, 0xff, USER_STASH_PARTITION_HEADER_SIZE);
			}
		}
	}
	else
		gkt_info("user_stash_init: partition not exist!\n");
}

